package com.edu.chat.web.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.edu.buservice.util.model.TenantUtil;
import com.edu.chat.web.common.enums.ResultCodeEnum;
import com.edu.chat.web.common.model.BizException;
import com.edu.chat.web.common.model.R;
import com.edu.chat.web.common.utils.MinioUtil;
import com.edu.chat.web.common.utils.TextClearUtil;
import com.edu.chat.web.common.utils.TextSplitterTool;
import com.edu.chat.web.covert.DocumentCovert;
import com.edu.chat.web.covert.FileCovert;
import com.edu.chat.web.dto.DocumentDTO;
import com.edu.chat.web.dto.FileDTO;
import com.edu.chat.web.dto.UploadFileDTO;
import com.edu.chat.web.integration.MilvusClient;
import com.edu.chat.web.mapper.CsFileMapper;
import com.edu.chat.web.model.CsFile;
import com.edu.chat.web.model.DocEntity;
import com.edu.chat.web.service.CsCategoriesService;
import com.edu.chat.web.service.CsDocumentsService;
import com.edu.chat.web.service.CsFileService;
import dev.langchain4j.data.document.Metadata;
import dev.langchain4j.data.segment.TextSegment;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 *
 */
@Slf4j
@Service
public class CsFileServiceImpl extends ServiceImpl<CsFileMapper, CsFile>
		implements CsFileService{

	@Autowired
	private MinioUtil minioUtil;

	@Autowired
	@Lazy
	private CsCategoriesService csCategoriesService;

	@Autowired
	private TextSplitterTool textSplitterTool;

	@Autowired
	private CsDocumentsService csDocumentsService;

	@Autowired
	private MilvusClient milvusClient;

	private final ThreadPoolExecutor asyncTaskThreadPool= new ThreadPoolExecutor(
			2,
			5,
			60,
			TimeUnit.SECONDS,
			new LinkedBlockingQueue<>(100),
			new ThreadPoolExecutor.CallerRunsPolicy()
	);

	@SneakyThrows
	@Override
	public R<String> upload(UploadFileDTO file) {
		log.info("FileServiceImpl upload start, file:{}", file);

		// 1. 增强参数校验
		if (Objects.isNull(file) || Objects.isNull(file.getMultipartFile()) || file.getMultipartFile().isEmpty()) {
			log.warn("Invalid file upload request");
			throw new BizException(ResultCodeEnum.PARAM_INVALID);
		}

		try {
			// 2. 生成唯一文件名
			String originalFilename = file.getFileName();
			String uniqueFileName = generateUniqueFileName(originalFilename);

			// 3. 上传文件并校验结果
			minioUtil.upload(file.getMultipartFile(), uniqueFileName);
			String fileUrl = minioUtil.getFileUrl(uniqueFileName);

			if (StringUtils.isBlank(fileUrl)) {
				log.error("File upload failed, filename: {}", uniqueFileName);
				throw new BizException(ResultCodeEnum.FAIL);
			}

			file.setFilePath(fileUrl);
			file.setObjectName(uniqueFileName);
			log.info("File uploaded successfully, URL: {}", fileUrl);


			// 5. 同步元数据上传
			R<Long> metaDataResult = uploadMetaData(file);
			if (!metaDataResult.isSuccess()) {
				log.error("Metadata synchronization failed");
				return R.fail(metaDataResult.getCode(), metaDataResult.getMsg());
			}

			file.setFileId(String.valueOf(metaDataResult.getData()));

			// 4. 异步处理使用自定义线程池
			CompletableFuture.runAsync(() ->
							processDocumentAsync(file, TenantUtil.getTenantId(), TenantUtil.getUserId()),
					asyncTaskThreadPool
			).exceptionally(ex -> {
				log.error("Async document processing failed", ex);
				return null;
			});
			return R.success(uniqueFileName);
		} catch (BizException e) {
			throw e;
		} catch (Exception e) {
			log.error("File upload unexpected error", e);
			throw new BizException(ResultCodeEnum.FAIL);
		}
	}

	/**
	 * 异步处理文档的方法。
	 *
	 * 该方法接收一个上传的文件、租户ID和用户ID，通过解析文件内容生成文档片段，
	 * 并将这些片段转换为文档对象后批量存储到数据库中，同时将相关数据插入到Milvus向量数据库中。
	 *
	 * @param file     包含上传文件信息的对象，类型为UploadFileDTO，需包含多部分文件流。
	 * @param tenantId 租户ID，用于设置当前线程的租户上下文。
	 * @param userId   用户ID，用于设置当前线程的用户上下文。
	 */
	private void processDocumentAsync(UploadFileDTO file, String tenantId, String userId) {
	    try (InputStream stream = file.getMultipartFile().getInputStream()) { // 自动关闭资源
	        // 设置当前线程的租户和用户上下文
	        TenantUtil.setCurrentTenantId(tenantId);
	        TenantUtil.setCurrentUserId(userId);

	        // 使用Tika工具将文件内容分割为文本片段
	        List<TextSegment> textSegments = textSplitterTool.splitTextByTika(stream);
	        List<DocumentDTO> documentDTOList = new ArrayList<>(textSegments.size());

	        // 遍历文本片段，将其转换为文档DTO对象并添加到列表中
	        for (TextSegment segment : textSegments) {
	            DocumentDTO dto = convertToDocumentDTO(segment, file);
	            if (dto != null) {
	                documentDTOList.add(dto);
	            }
	        }

	        // 如果文档DTO列表不为空，则批量创建文档并记录日志
	        if (!documentDTOList.isEmpty()) {
	            csDocumentsService.createDocumentsBatch(documentDTOList);
	            log.info("Processed {} document segments", documentDTOList.size());

	            // 将文档DTO列表转换为实体对象列表，并插入到Milvus向量数据库中
	            List<DocEntity> docEntities = documentDTOList.stream()
	                    .map(DocumentCovert::covert)
	                    .collect(Collectors.toList());

	            milvusClient.insertDocData(docEntities);
	        }
	    } catch (Exception e) {
	        // 捕获异常并记录错误日志
	        log.error("Document processing failed", e);
	    } finally {
	        // 清除当前线程的租户和用户上下文
	        TenantUtil.clear();
	    }
	}

	/** 转换文本分段为DTO */
	private DocumentDTO convertToDocumentDTO(TextSegment segment,final UploadFileDTO file) {
		try {
			DocumentDTO dto = new DocumentDTO();

			String text = segment.text();

			//去掉空格和换行符
			dto.setContent(TextClearUtil.cleanText(text));
			dto.setLibraryId(Long.valueOf(file.getLibraryId()));
			dto.setFileId(Long.valueOf(file.getFileId()));
			dto.setId(IdUtil.getSnowflakeNextId());

			Metadata metadata = segment.metadata();
			if (metadata != null) {
				DocumentDTO.MetaDataDTO metaDataDTO = new DocumentDTO.MetaDataDTO();

				Optional.ofNullable(metadata.getInteger("xmpTPg:NPages"))
						.ifPresent(metaDataDTO::setPageSize);

				Optional.ofNullable(metadata.getInteger("index"))
						.ifPresent(metaDataDTO::setIndex);

				dto.setMetadata(metaDataDTO);
			}
			return dto;
		} catch (Exception e) {
			log.warn("Failed to convert text segment", e);
			return null;
		}
	}



	@Override
	public R<Long> uploadMetaData(UploadFileDTO file) {
		log.info("FileServiceImpl uploadMetaData:{}", file);
		if(Objects.isNull(file)){
			throw new BizException(ResultCodeEnum.PARAM_INVALID);
		}

		//如果categoryId 为空，获取根节点
		if(StringUtils.isBlank(file.getCategoryId())){
			String s = csCategoriesService.queryRootCategoryId(file.getLibraryId());
			file.setCategoryId(s);
		}


		CsFile csFile = FileCovert.buildCsFile(file);

		csFile.setId(IdUtil.getSnowflakeNextId());
		save(csFile);


		return R.success(csFile.getId());
	}

	@Override
	public List<FileDTO> listFileByCategoryId(String categoryId,String libraryId) {

		if (StringUtils.isBlank(categoryId) || StringUtils.isBlank(libraryId)){
			throw new BizException(ResultCodeEnum.PARAM_INVALID, "categoryId or libraryId is blank");
		}

		LambdaQueryWrapper<CsFile> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(CsFile::getCategoryId, categoryId);
		queryWrapper.eq(CsFile::getLibraryId, libraryId);
		queryWrapper.eq(CsFile::getDeleted, 0);

		List<CsFile> list = list(queryWrapper);

		if(CollectionUtil.isEmpty(list)){
			log.info("FileServiceImpl listFileByCategoryId:{}", list);
			return Collections.emptyList();
		}

		return list.stream().map(FileCovert::covert).toList();
	}

	@Override
	public R<FileDTO> getFileById(String id) {
		if (StringUtils.isBlank(id)){
			throw new BizException(ResultCodeEnum.PARAM_INVALID, "id is blank");
		}
		if (getById(id) != null){
			return R.success(FileCovert.covert(getById(id)));
		}
		return R.status(false);
	}

	@Override
	public R<Boolean> deleteFile(String id) {

		if (StringUtils.isBlank(id)){
			throw new BizException(ResultCodeEnum.PARAM_INVALID, "id is blank");
		}

		//查看文件是否存在
		CsFile file = getById(id);

		if (ObjectUtils.allNull(file)){
			throw new BizException(ResultCodeEnum.PARAM_INVALID, "file is not exist");
		}

		//删除文件
		if (!removeById(id)){
			throw new BizException(ResultCodeEnum.PARAM_INVALID, "file delete fail");
		}

		//删除文件
		minioUtil.delete(file.getObjectName());

		//删除doc
		R<List<String>> listR = csDocumentsService.deleteDocuments(id);
		if (R.isSuccess(listR)){
			milvusClient.deleteFile(listR.getData());
		}
		return R.success(true);

	}


	/** 生成唯一文件名 */
	private String generateUniqueFileName(String originalFilename) {
		return UUID.randomUUID() + "_" + originalFilename;
	}



}




